/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::{collections::HashMap, mem, sync::Arc};

use glyph_rasterizer::{BaseFontInstance, SharedFontResources};

use crate::{
    api::{
        units::*, AsyncBlobImageRasterizer, BlobImageData, BlobImageDescriptor, BlobImageHandler,
        BlobImageKey, BlobImageParams, BlobImageRequest, DebugFlags, DirtyRect, FontTemplate,
        ImageDescriptor, TileSize,
    },
    image_tiling::*,
    profiler,
    render_api::{AddFont, ResourceUpdate, TransactionMsg},
};

/// We use this to generate the async blob rendering requests.
struct BlobImageTemplate {
    descriptor: ImageDescriptor,
    tile_size: TileSize,
    dirty_rect: BlobDirtyRect,
    /// See ImageResource::visible_rect.
    visible_rect: DeviceIntRect,
    // If the active rect of the blob changes, this represents the
    // range of tiles that remain valid. This must be taken into
    // account in addition to the valid rect when submitting blob
    // rasterization requests.
    // `None` means the bounds have not changed (tiles are still valid).
    // `Some(TileRange::zero())` means all of the tiles are invalid.
    valid_tiles_after_bounds_change: Option<TileRange>,
}

pub struct ApiResources {
    blob_image_templates: HashMap<BlobImageKey, BlobImageTemplate>,
    pub blob_image_handler: Option<Box<dyn BlobImageHandler>>,
    fonts: SharedFontResources,
    // This should only be true for CI or debugging purposes. If true,
    // we'll restrict the size of blob images as a result effectively
    // rendering them incorrectly.
    debug_restrict_blob_size: bool,
}

impl ApiResources {
    pub fn new(
        blob_image_handler: Option<Box<dyn BlobImageHandler>>,
        fonts: SharedFontResources,
    ) -> Self {
        ApiResources {
            blob_image_templates: HashMap::new(),
            blob_image_handler,
            fonts,
            debug_restrict_blob_size: false,
        }
    }

    pub fn get_fonts(&self) -> SharedFontResources {
        self.fonts.clone()
    }

    pub fn set_debug_flags(&mut self, flags: DebugFlags) {
        self.debug_restrict_blob_size = flags.contains(DebugFlags::RESTRICT_BLOB_SIZE);
    }

    pub fn adjust_blob_visible_rect(
        &self,
        rect: &mut DeviceIntRect,
        size: Option<&mut DeviceIntSize>,
    ) {
        if self.debug_restrict_blob_size {
            rect.max.x = rect.max.x.min(rect.min.x + 2048);
            rect.max.y = rect.max.y.min(rect.min.y + 2048);
            if let Some(size) = size {
                size.width = size.width.min(2048);
                size.height = size.height.min(2048);
            }
        }
    }

    pub fn update(&mut self, transaction: &mut TransactionMsg) {
        let mut blobs_to_rasterize = Vec::new();
        for update in &mut transaction.resource_updates {
            match *update {
                ResourceUpdate::AddBlobImage(ref mut img) => {
                    self.adjust_blob_visible_rect(
                        &mut img.visible_rect,
                        Some(&mut img.descriptor.size),
                    );
                    self.blob_image_handler
                        .as_mut()
                        .expect("no blob image handler")
                        .add(
                            img.key,
                            Arc::clone(&img.data),
                            &img.visible_rect,
                            img.tile_size,
                        );

                    self.blob_image_templates.insert(
                        img.key,
                        BlobImageTemplate {
                            descriptor: img.descriptor,
                            tile_size: img.tile_size,
                            dirty_rect: DirtyRect::All,
                            valid_tiles_after_bounds_change: None,
                            visible_rect: img.visible_rect,
                        },
                    );
                    blobs_to_rasterize.push(img.key);
                }
                ResourceUpdate::UpdateBlobImage(ref mut img) => {
                    debug_assert_eq!(img.visible_rect.size(), img.descriptor.size);
                    self.adjust_blob_visible_rect(
                        &mut img.visible_rect,
                        Some(&mut img.descriptor.size),
                    );
                    self.update_blob_image(
                        img.key,
                        Some(&img.descriptor),
                        Some(&img.dirty_rect),
                        Some(Arc::clone(&img.data)),
                        &img.visible_rect,
                    );
                    blobs_to_rasterize.push(img.key);
                }
                ResourceUpdate::DeleteBlobImage(key) => {
                    transaction.use_scene_builder_thread = true;
                    self.blob_image_templates.remove(&key);
                    if let Some(ref mut handler) = self.blob_image_handler {
                        handler.delete(key);
                    }
                }
                ResourceUpdate::SetBlobImageVisibleArea(ref key, ref mut area) => {
                    self.adjust_blob_visible_rect(area, None);
                    self.update_blob_image(*key, None, None, None, &area);
                    blobs_to_rasterize.push(*key);
                }
                ResourceUpdate::AddFont(ref font) => {
                    let (key, parsed_font) = match font {
                        AddFont::Parsed(key, parsed_font) => (key, parsed_font.clone()),
                    };
                    if let Some(shared_key) = self.fonts.font_keys.add_key(*key) {
                        self.fonts
                            .templates
                            .add_parsed_font(shared_key, parsed_font);
                    }
                }
                ResourceUpdate::AddFontInstance(ref mut instance) => {
                    let shared_font_key = self.fonts.font_keys.map_key(&instance.font_key);
                    assert!(self.fonts.templates.has_font(&shared_font_key));
                    // AddFontInstance will only be processed here, not in the resource cache, so it
                    // is safe to take the options rather than clone them.
                    let base = Arc::new(BaseFontInstance::new(
                        instance.key,
                        shared_font_key,
                        instance.glyph_size,
                        mem::take(&mut instance.options),
                        mem::take(&mut instance.platform_options),
                        mem::take(&mut instance.variations),
                    ));
                    if let Some(_shared_key) = self.fonts.instance_keys.add_key(base.clone()) {
                        self.fonts.instances.add_font_instance(base);
                    }
                }
                ResourceUpdate::DeleteFont(_key) => {
                    transaction.use_scene_builder_thread = true;
                }
                ResourceUpdate::DeleteFontInstance(_key) => {
                    transaction.use_scene_builder_thread = true;
                    // We will delete from the shared font instance map in the resource cache
                    // after scene swap.
                }
                ResourceUpdate::DeleteImage(..) => {
                    transaction.use_scene_builder_thread = true;
                }
                _ => {}
            }
        }

        let (rasterizer, requests) = self.create_blob_scene_builder_requests(&blobs_to_rasterize);
        transaction
            .profile
            .set(profiler::RASTERIZED_BLOBS, blobs_to_rasterize.len());
        transaction
            .profile
            .set(profiler::RASTERIZED_BLOB_TILES, requests.len());
        transaction.use_scene_builder_thread |= !requests.is_empty();
        transaction.use_scene_builder_thread |= !transaction.scene_ops.is_empty();
        transaction.blob_rasterizer = rasterizer;
        transaction.blob_requests = requests;
    }

    pub fn enable_multithreading(&mut self, enable: bool) {
        if let Some(ref mut handler) = self.blob_image_handler {
            handler.enable_multithreading(enable);
        }
    }

    fn update_blob_image(
        &mut self,
        key: BlobImageKey,
        descriptor: Option<&ImageDescriptor>,
        dirty_rect: Option<&BlobDirtyRect>,
        data: Option<Arc<BlobImageData>>,
        visible_rect: &DeviceIntRect,
    ) {
        if let Some(data) = data {
            let dirty_rect = dirty_rect.expect("no dirty rect");
            self.blob_image_handler
                .as_mut()
                .expect("no blob image handler")
                .update(key, data, visible_rect, dirty_rect);
        }

        let image = self
            .blob_image_templates
            .get_mut(&key)
            .expect("Attempt to update non-existent blob image");

        let mut valid_tiles_after_bounds_change = compute_valid_tiles_if_bounds_change(
            &image.visible_rect,
            visible_rect,
            image.tile_size,
        );

        match (
            image.valid_tiles_after_bounds_change,
            valid_tiles_after_bounds_change,
        ) {
            (Some(old), Some(ref mut new)) => {
                *new = new.intersection(&old).unwrap_or_else(TileRange::zero);
            }
            (Some(old), None) => {
                valid_tiles_after_bounds_change = Some(old);
            }
            _ => {}
        }

        let blob_size = visible_rect.size();

        if let Some(descriptor) = descriptor {
            image.descriptor = *descriptor;
        } else {
            // make sure the descriptor size matches the visible rect.
            // This might not be necessary but let's stay on the safe side.
            image.descriptor.size = blob_size;
        }

        if let Some(dirty_rect) = dirty_rect {
            image.dirty_rect = image.dirty_rect.union(dirty_rect);
        }

        image.valid_tiles_after_bounds_change = valid_tiles_after_bounds_change;
        image.visible_rect = *visible_rect;
    }

    pub fn create_blob_scene_builder_requests(
        &mut self,
        keys: &[BlobImageKey],
    ) -> (
        Option<Box<dyn AsyncBlobImageRasterizer>>,
        Vec<BlobImageParams>,
    ) {
        if self.blob_image_handler.is_none() || keys.is_empty() {
            return (None, Vec::new());
        }

        let mut blob_request_params = Vec::new();
        for key in keys {
            let template = self
                .blob_image_templates
                .get_mut(key)
                .expect("no blob image template");

            // If we know that only a portion of the blob image is in the viewport,
            // only request these visible tiles since blob images can be huge.
            let tiles = compute_tile_range(&template.visible_rect, template.tile_size);

            // Don't request tiles that weren't invalidated.
            let dirty_tiles = match template.dirty_rect {
                DirtyRect::Partial(dirty_rect) => {
                    compute_tile_range(&dirty_rect.cast_unit(), template.tile_size)
                }
                DirtyRect::All => tiles,
            };

            for_each_tile_in_range(&tiles, |tile| {
                let still_valid = template
                    .valid_tiles_after_bounds_change
                    .map(|valid_tiles| valid_tiles.contains(tile))
                    .unwrap_or(true);

                if still_valid && !dirty_tiles.contains(tile) {
                    return;
                }

                let descriptor = BlobImageDescriptor {
                    rect: compute_tile_rect(&template.visible_rect, template.tile_size, tile)
                        .cast_unit(),
                    format: template.descriptor.format,
                };

                assert!(descriptor.rect.width() > 0 && descriptor.rect.height() > 0);
                blob_request_params.push(BlobImageParams {
                    request: BlobImageRequest { key: *key, tile },
                    descriptor,
                    dirty_rect: DirtyRect::All,
                });
            });

            template.dirty_rect = DirtyRect::empty();
            template.valid_tiles_after_bounds_change = None;
        }

        let handler = self
            .blob_image_handler
            .as_mut()
            .expect("no blob image handler");
        handler.prepare_resources(&self.fonts, &blob_request_params);
        (Some(handler.create_blob_rasterizer()), blob_request_params)
    }
}
